#include "CourseObject.h"

#include "LineType.h"
#include "RangeObject.h"

OBJECT_DEFINE_START (CourseObject, course_object)

void
course_object_init (CourseObject* obj)
{
        object_init (OBJECT (obj));
        object_set_destory_func (obj, course_object_destory);

        for (int i = 0; i < 15; ++i)
                for (int j = 0; j < 8; ++j)
                        obj->table[i][j] = object_string_new ();
        obj->sch                   = NULL;
        obj->top_mark_head         = NULL;
        obj->course_top_mark_start = NULL;
        obj->start_date            = NULL;
        obj->today                 = date_object_new_with_today ();
        obj->_error                = object_string_new ();
        obj->current_week          = 0;
        obj->student_info          = NULL;
        obj->is_init               = false;
}

void
course_object_destruction (CourseObject* obj)
{
        for (int i = 0; i < 15; ++i)
                for (int j = 0; j < 8; ++j)
                        object_unref (obj->table[i][j]);
        object_unref (obj->sch);
        object_unref (obj->top_mark_head);
        object_unref (obj->today);
        object_unref (obj->student_info);
        object_unref (obj->start_date);
        object_unref (obj->_error);
}

int
course_object_load_sch (CourseObject* obj, const char* sch_file_path)
{
        int   state;
        long  file_length;
        FILE* fp;

        if (!obj || !sch_file_path) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   load_sch,
                                   "传入参数出错！CourseObject<%p>，sch_"
                                   "file_path<%p>\n",
                                   obj,
                                   sch_file_path);
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         load_sch,
                                         "传入参数出错！CourseObject<%p>，sch_"
                                         "file_path<%p>\n",
                                         obj,
                                         sch_file_path);
                return 1;
        }
        fp = fopen (sch_file_path, "r");
        if (!fp) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   load_sch,
                                   "打开 '%s' 出错！\n",
                                   sch_file_path);
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         load_sch,
                                         "打开 '%s' 出错！\n",
                                         sch_file_path);
                return 1;
        }
        fseek (fp, 0, SEEK_END);
        file_length = ftell (fp);
        if (file_length < 5) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   load_sch,
                                   "文件内容过短，其中不包含实际内容。\n");
                fclose (fp);
                return 1;
        }
        obj->sch                          = object_string_application_new ();
        OBJECT_STRING (obj->sch)->length  = file_length;
        OBJECT_STRING (obj->sch)->charset = (char*)malloc (file_length + 1);
        OBJECT_STRING (obj->sch)->charset[file_length] = '\0';
        rewind (fp);
        state = (int)
                fread (OBJECT_STRING (obj->sch)->charset, file_length, 1, fp);
        if (state == 0) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   load_sch,
                                   "读取文件 '%s' 时发生错误。\n",
                                   sch_file_path);
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         load_sch,
                                         "读取文件 '%s' 时发生错误。\n",
                                         sch_file_path);
                fclose (fp);
                return 1;
        }
        fclose (fp);
        object_string_application_set_segment_charset (obj->sch, '\n');
        // object_string_application_rewind (obj->sch);
        OBJECT_INFO_TOOL (CourseObject,
                          load_sch,
                          "打开 '%s' 成功！\n",
                          sch_file_path);
        return 0;
}

static ObjectStringApplication*
course_object_getln (CourseObject* obj)
{
        if (!obj) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   getln,
                                   "传入参数有误！无对象。\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         getln,
                                         "传入参数有误！无对象。\n");
                return NULL;
        }
        if (!obj->sch) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   getln,
                                   "对象没有打开任何文件！\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         getln,
                                         "对象没有打开任何文件！\n");
                return NULL;
        }
        return object_string_application_get_string_before_segment (obj->sch);
}

static void
course_object_add_top_mark (CourseObject* obj, TopMarkObject* top_mark)
{
        if (!obj || !top_mark) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   add_top_mark,
                                   "传入参数有误！CourseObject<%p>"
                                   "，TopMarkObject<%p>\n",
                                   obj,
                                   top_mark);
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         add_top_mark,
                                         "传入参数有误！CourseObject<%p>"
                                         "，TopMarkObject<%p>\n",
                                         obj,
                                         top_mark);
                return;
        }
        if (!obj->top_mark_head) {
                obj->top_mark_head =
                        object_reference_to (top_mark, TOP_MARK_OBJECT);
                object_node_set_as_manager (OBJECT_NODE (top_mark));
                return;
        }
        object_node_set_new_end (OBJECT_NODE (obj->top_mark_head),
                                 object_reference_to (top_mark, OBJECT_NODE));
}

static int
course_object_check_struct (CourseObject* obj)
{
        TopMarkObject* top_mark;
        LabelObject*   label;
        DateObject*    start_date = NULL;
        ObjectString * stu_grade = NULL, *stu_student = NULL, *stu_class = NULL;

        if (!obj) {
                OBJECT_ERROR_TOOL (CourseObject, check_struct, "对象为空！\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         check_struct,
                                         "对象为空！\n");
                return 1;
        }
        if (!obj->top_mark_head) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "尚未载入任何课程表内容！\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         check_struct,
                                         "尚未载入任何课程表内容！\n");
                return 1;
        }

        top_mark = obj->top_mark_head;
        if (!top_mark || top_mark->type != TOP_MARK_TYPE_SCHOOL) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "解析出错: SCH 文件第一个大标签必须是 "
                                   "\033[01;33m[School Time Table]\033[0m\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         check_struct,
                                         "解析出错: SCH 文件第一个大标签必须是 "
                                         "[School Time Table]\n");
                return 1;
        }
        label = top_mark->label_head;
        while (label) {
                if (label->value_type == VALUE_TYPE_DATE) {
                        start_date = label->value_head->date;
                        break;
                }
                label = LABEL_OBJECT (
                        object_node_get_next (OBJECT_NODE (label)));
        }
        if (!start_date) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "课程表第一周星期一的时间未在 [School Time "
                                   "Table] 中指出！无法继续。\n");
                OBJECT_STRING_ERROR_LOG (
                        obj->_error,
                        CourseObject,
                        check_struct,
                        "课程表第一周星期一的时间未在 [School Time "
                        "Table] 中指出！无法继续。\n");
                return 1;
        }
        obj->start_date = object_reference_to (start_date, DATE_OBJECT);
        /* 推算当前周 */
        obj->current_week = date_object_get_week_of_today (start_date);
        if (obj->current_week == 0 || obj->current_week > 20) {
                OBJECT_WARNING_TOOL (
                        CourseObject,
                        check_struct,
                        "当前周有误(第 %d 周)，故显示第一周的课程！\n",
                        obj->current_week);
                obj->current_week = 1;
        }

        /*载入个人信息*/
        top_mark =
                TOP_MARK_OBJECT (object_node_get_next (OBJECT_NODE (top_mark)));
        if (!top_mark) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "无 [Student Info]，解析退出。\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         check_struct,
                                         "无 [Student Info]，解析退出。\n");
                return 1;
        }
        if (top_mark->type != TOP_MARK_TYPE_STUDENT) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "[School Time Table] 的下一个大标签应该为 "
                                   "[Student Info]。解析已退出。\n");
                OBJECT_STRING_ERROR_LOG (
                        obj->_error,
                        CourseObject,
                        check_struct,
                        "[School Time Table] 的下一个大标签应该为 "
                        "[Student Info]。解析已退出。\n");
                return 1;
        }
        label = top_mark->label_head;
        if (!label) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   check_struct,
                                   "[Student Info] "
                                   "标签不包含任何一条信息！请包含基本学生信息"
                                   "后再运行。\n");
                OBJECT_STRING_ERROR_LOG (
                        obj->_error,
                        CourseObject,
                        check_struct,
                        "[Student Info] "
                        "标签不包含任何一条信息！请包含基本学生信息"
                        "后再运行。\n");
                return 1;
        }
        while (label) {
                if (object_string_compare_with_charset (label->key, "Student"))
                        stu_student = label->value_head->text;
                if (object_string_compare_with_charset (label->key, "Grade"))
                        stu_grade = label->value_head->text;
                if (object_string_compare_with_charset (label->key, "Class"))
                        stu_class = label->value_head->text;
                label = LABEL_OBJECT (
                        object_node_get_next (OBJECT_NODE (label)));
        }
        /*取出学生信息当作顶栏*/
        obj->student_info = object_string_new ();
        object_string_append_string (obj->student_info,
                                     (stu_grade ? stu_grade->charset : ""));
        object_string_append_string (obj->student_info,
                                     (stu_class ? stu_class->charset : ""));
        object_string_append_string (obj->student_info,
                                     (stu_student ? stu_student->charset : ""));
        object_string_append_string (obj->student_info, "的课程表");

        OBJECT_INFO_TOOL (CourseObject,
                          check_struct,
                          "课表基本结构分析结束。\n",
                          obj->student_info->charset);
        obj->course_top_mark_start =
                TOP_MARK_OBJECT (object_node_get_next (OBJECT_NODE (top_mark)));
        obj->is_init = true;
        return 0;
}

int
course_object_analysis_sch (CourseObject* obj)
{
        int                      state;
        ObjectStringApplication* str_line = NULL;
        LineType                 line_type;
        TopMarkType              top_mark_type;
        TopMarkObject*           top_mark              = NULL;
        ObjectString*            current_top_mark_name = NULL;
        if (!obj->sch) {
                OBJECT_ERROR_TOOL (CourseObject,
                                   analysis_sch,
                                   "对象尚未打开任何文件！\n");
                OBJECT_STRING_ERROR_LOG (obj->_error,
                                         CourseObject,
                                         analysis_sch,
                                         "对象尚未打开任何文件！\n");
                return 1;
        }
        object_string_application_rewind (obj->sch);

reanalysis:
        object_unref (str_line);
        str_line  = course_object_getln (obj);
        line_type = line_fetch_type (str_line);
        switch (line_type) {
        case LINE_TYPE_END:
                OBJECT_INFO_TOOL (CourseObject,
                                  analysis_sch,
                                  "文件分析已结束。\n");
                object_unref (current_top_mark_name);
                object_unref (top_mark);
                goto check_main_struct;
        case LINE_TYPE_KEYVALUE: {
                if (!top_mark) {
                        OBJECT_ERROR_TOOL (
                                CourseObject,
                                analysis_sch,
                                "尚未检测到[大标签]！无法继续解析。\n");
                        OBJECT_STRING_ERROR_LOG (
                                obj->_error,
                                CourseObject,
                                analysis_sch,
                                "尚未检测到[大标签]！无法继续解析。\n");
                        object_unref (current_top_mark_name);
                        object_unref (str_line);
                        return 1;
                }
                state = top_mark_object_analysis_keyvalue_line (top_mark,
                                                                str_line);
                if (state != 0) {
                        OBJECT_ERROR_TOOL (
                                CourseObject,
                                analysis_sch,
                                "当前大标签 \033[01;32m%s\033["
                                "0m 解析数值行出现问题，将停止解析。\n",
                                object_string_get_string (
                                        current_top_mark_name));
                        OBJECT_STRING_ERROR_LOG (
                                obj->_error,
                                CourseObject,
                                analysis_sch,
                                "当前大标签 %s "
                                "解析数值行出现问题，将停止解析。\n",
                                object_string_get_string (
                                        current_top_mark_name));
                        object_unref (top_mark);
                        object_unref (current_top_mark_name);
                        object_unref (str_line);
                        return 1;
                }
                break;
        }
        case LINE_TYPE_TOP_MARK: {
                object_unref (top_mark);
                top_mark_type = top_mark_fetch_type (OBJECT_STRING (str_line));
                if (top_mark_type == TOP_MARK_TYPE_OTHER) {
                        OBJECT_ERROR_TOOL (
                                CourseObject,
                                analysis_sch,
                                "当前大标签 \033[01;32m%s\033["
                                "0m 之后识别到不正确的另一个大标签: "
                                "'\033[01;31m%s\033[0m'，已停止解析。\n",
                                object_string_get_string (
                                        current_top_mark_name),
                                object_string_get_string (
                                        OBJECT_STRING (str_line)));

                        OBJECT_STRING_ERROR_LOG (
                                obj->_error,
                                CourseObject,
                                analysis_sch,
                                "当前大标签 %s 之后识别到不正确的另一个大标签: "
                                "'%s'，已停止解析。\n",
                                object_string_get_string (
                                        current_top_mark_name),
                                object_string_get_string (
                                        OBJECT_STRING (str_line)));
                        object_unref (str_line);
                        object_unref (current_top_mark_name);
                        return 1;
                }

                top_mark       = top_mark_object_new ();
                top_mark->type = top_mark_type;
                if (top_mark_type == TOP_MARK_TYPE_COURSE) {
                        top_mark_object_register_as_course_info (top_mark);
                } else if (top_mark_type == TOP_MARK_TYPE_SCHOOL) {
                        top_mark_object_register_as_school_time_table (
                                top_mark);
                } else {
                        top_mark_object_register_as_student_info (top_mark);
                }

                course_object_add_top_mark (obj, top_mark);

                object_unref (current_top_mark_name);
                current_top_mark_name = object_string_new_with (
                        OBJECT_STRING (str_line)->charset);
                break;
        }
        case TOP_MARK_TYPE_OTHER:
                OBJECT_ERROR_TOOL (
                        CourseObject,
                        analysis_sch,
                        "当前大标签 %s "
                        "之后检测到不能正确识别的行: "
                        "'%s'，已停止解析。\n",
                        object_string_get_string (current_top_mark_name),
                        object_string_get_string (OBJECT_STRING (str_line)));
                OBJECT_STRING_ERROR_LOG (
                        obj->_error,
                        CourseObject,
                        analysis_sch,
                        "当前大标签 %s "
                        "之后检测到不能正确识别的行: "
                        "'%s'，已停止解析。\n",
                        object_string_get_string (current_top_mark_name),
                        object_string_get_string (OBJECT_STRING (str_line)));
                object_unref (str_line);
                object_unref (top_mark);
                object_unref (current_top_mark_name);
                return 1;
        default:
                break;
        }
        goto reanalysis;

check_main_struct:
        /*检查主要结构*/
        return course_object_check_struct (obj);
}

static void
course_object_add_course_to_grid (ObjectString* grid[][8],
                                  DateObject*   start_date,
                                  DateObject*   date,
                                  ValueObject*  week,
                                  ValueObject*  section,
                                  ValueObject*  day,
                                  ObjectString* course_info,
                                  int           current_week)
{
        bool is_in_week = false, is_in_day = false, is_in_section = false;
        int  _week, _day = 0;
        if (date) {
                _week = date_object_get_week_of_other_date (start_date, date);
                if (_week == current_week)
                        is_in_week = true;
                _day = date_object_get_day_of_week (start_date, date);
        } else {
                while (week) {
                        if (week->number->number_type == VALUE_TYPE_RANGE) {
                                is_in_week = range_object_is_number_in_range (
                                        RANGE_OBJECT (week->number),
                                        current_week);
                        } else {
                                is_in_week =
                                        (week->number->number == current_week
                                                 ? true
                                                 : false);
                        }
                        if (is_in_week)
                                break;
                        week = VALUE_OBJECT (
                                object_node_get_next (OBJECT_NODE (week)));
                }
        }
        /*本周不包含本课程，则返回*/
        if (!is_in_week)
                return;
        for (int sec = 1; sec <= 14; ++sec) {
                for (int d = 1; d <= 7; ++d) {
                        if (value_object_is_contain_number (section, sec))
                                is_in_section = true;
                        if (!_day && value_object_is_contain_number (day, d))
                                is_in_day = true;
                        else if (_day && _day == d)
                                is_in_day = true;
                        if (is_in_day && is_in_section) {
                                object_string_set_string (
                                        grid[sec][d],
                                        (course_info ? course_info->charset
                                                     : "nil"));
                        }
                        is_in_day     = false;
                        is_in_section = false;
                }
        }
}

void
course_object_add_all_course_in_current_week (CourseObject* course)
{
        char buf[20];
        char _day_buf[][9] = {"", "一", "二", "三", "四", "五", "六", "日"};
        DateObject*    date;
        ValueObject *  week, *section, *day, *dt;
        ObjectString*  str_cache = NULL;
        TopMarkObject* top_mark  = course->course_top_mark_start;
        LabelObject*   label;

        date_object_get_monday_of_current_week (course->start_date,
                                                course->current_week,
                                                course->today);
        /*清除所有的方格*/
        for (int i = 0; i < 15; ++i) {
                for (int j = 0; j < 8; ++j) {
                        if (i == 0) {
                                object_string_set_string (course->table[i][j],
                                                          _day_buf[j]);
                                if (j > 0) {
                                        sprintf (buf,
                                                 "\n(%d.%d)",
                                                 course->today->month,
                                                 course->today->day);
                                        object_string_append_string (
                                                course->table[i][j],
                                                buf);
                                        date_object_add_days (course->today, 1);
                                } else {
                                        sprintf (buf,
                                                 "%d ",
                                                 course->today->year);
                                        object_string_append_string (
                                                course->table[i][j],
                                                buf);
                                        object_string_append_string (
                                                course->table[i][j],
                                                "年");
                                }
                        } else if (j == 0) {
                                sprintf (buf, "%d", i);
                                object_string_set_string (course->table[i][j],
                                                          buf);
                        } else {
                                object_string_set_string (course->table[i][j],
                                                          "");
                        }
                }
        }

fetch_next_top_mark:
        object_unref (str_cache);
        if (!top_mark)
                return;
        date    = NULL;
        week    = NULL;
        section = NULL;
        day     = NULL;

        if (top_mark->type != TOP_MARK_TYPE_COURSE) {
                OBJECT_WARNING_TOOL (CourseObject,
                                     window,
                                     "识别到了不属于 [Course Info] 的 "
                                     "TopMark！TopMarkName: %s\n",
                                     top_mark->top_mark_name->charset);
                OBJECT_STRING_ERROR_LOG (course->_error,
                                         CourseObject,
                                         window,
                                         "识别到了不属于 [Course Info] 的 "
                                         "TopMark！TopMarkName: %s\n",
                                         top_mark->top_mark_name->charset);
                top_mark = TOP_MARK_OBJECT (
                        object_node_get_next (OBJECT_NODE (top_mark)));
                goto fetch_next_top_mark;
        }

        label     = top_mark->label_head;
        str_cache = object_string_new ();
        while (label) {
                if (label->value_type == VALUE_TYPE_TEXT) {
                        object_string_append_string (
                                str_cache,
                                label->value_head->text->charset);
                        object_string_append_string (str_cache, "\n");
                }
                if (label->value_type == VALUE_TYPE_DATE) {
                        date = label->value_head->date;
                        dt   = label->value_head;
                }
                if (label->value_type == VALUE_TYPE_NUMBER) {
                        if (object_string_compare_with_charset (label->key,
                                                                "Week")) {
                                week = label->value_head;
                        }
                        if (object_string_compare_with_charset (label->key,
                                                                "Section")) {
                                section = label->value_head;
                        }
                        if (object_string_compare_with_charset (label->key,
                                                                "Day")) {
                                day = label->value_head;
                        }
                }

                label = LABEL_OBJECT (
                        object_node_get_next (OBJECT_NODE (label)));
        }

re_add_date:
        course_object_add_course_to_grid (course->table,
                                          course->start_date,
                                          date,
                                          week,
                                          section,
                                          day,
                                          str_cache,
                                          course->current_week);
        if (date) {
                dt   = VALUE_OBJECT (object_node_get_next (OBJECT_NODE (dt)));
                date = (dt ? dt->date : NULL);
                if (date)
                        goto re_add_date;
        }

        top_mark =
                TOP_MARK_OBJECT (object_node_get_next (OBJECT_NODE (top_mark)));
        goto fetch_next_top_mark;
}

void
course_object_show_course_of_prev_week (CourseObject* course)
{
        if (course->current_week <= 1)
                return;
        course->current_week -= 1;
        course_object_add_all_course_in_current_week (course);
}

void
course_object_show_course_of_next_week (CourseObject* course)
{
        if (course->current_week >= 20)
                return;
        course->current_week += 1;
        course_object_add_all_course_in_current_week (course);
}
